# -*- coding: utf-8 -*-

import logging
from socketio.namespace import BaseNamespace
from socketio.mixins import RoomsMixin, BroadcastMixin
from game.mixins import GamesMixin
from socketio.sdjango import namespace
from game.models import *
from game.models.utils import *
from chat.models import *
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from datetime import datetime, timedelta
from gevent.greenlet import Greenlet

import pdb

@namespace('/game')
class GameNamespace(BaseNamespace, GamesMixin, BroadcastMixin):

    # Actualización automática del mapa de batalla, cada 30 segundos (lo hago por los 'resources_limit' de un hormiguero).
    # private
    def refresh_map(self, game):
        self.emit_map_to_game(game)
        Greenlet(self.refresh_map, game).start_later(30)

    def emit_update_delay(self, player):
        player = Player.objects.get(id=self.socket.session['player'])
        emit_time = datetime.now().strftime('%d %m %Y %H:%M:%S.%f')
        self.emit_to_player('delay', emit_time, player.delay)
        Greenlet(self.emit_update_delay, player).start_later(30)

    def on_update_delay(self, emit_time):
        emit_time = datetime.strptime(emit_time, '%d %m %Y %H:%M:%S.%f')
        get_time = datetime.now()
        player = Player.objects.get(id=self.socket.session['player'])
        delay = (get_time - emit_time).total_seconds() / 2
        alfa = 0.7
        delay_final = (delay * alfa) + (player.delay * (1-alfa))
        player.delay = delay_final
        player.save()
        player.game.update_delay()


    """2
    # ANTES
    self.emit_event_to_player(attack, type_event):

    # AHORA
    delay = player.game.delay - player.delay
    Greenlet(self.emit_event_to_player, attack, type_event).start_later(delay)
    """


    # Actualización automática de los datos de cada hormiguero de un jugador (recursos, ejército).
    # private
    def refresh_base(self, player, new_time):

        if new_time == None: # Initial case
            new_time = datetime.now()
        else:
            # Update Resources
            old_time = new_time
            new_time = datetime.now()
            difference_seconds = (new_time - old_time).total_seconds()

            # Actualización de bases propias de un jugador (recursos, ejército)
            bases_ids, x_coordinates, y_coordinates, resources, ants = [], [], [], [], []
            speed_bonus = False
            for base in player.bases.all():
                bases_ids.append(base.id)
                x_coordinates.append(base.x_coordinate)
                y_coordinates.append(base.y_coordinate)
                ants.append(base.get_ants())

                # Update Resources
                resources_produced = base.get_production() * (difference_seconds/60)
                if (base.resources_limit - resources_produced) > 0:
                    base.resources += resources_produced
                    base.resources_limit -= resources_produced
                    base.save()
                resources.append(base.resources)

                if base.bonus == 'S':
                    speed_bonus = True

            self.emit_to_other_player(player, 'bases', bases_ids, x_coordinates, y_coordinates, resources, ants, speed_bonus)

        Greenlet(self.refresh_base, player, new_time).start_later(1)


    # Finalización de un 'movimiento de recursos'.
    # private
    def back_movement(self, movement_resources):
        if not movement_resources.status == 'N':
            if not movement_resources.status == 'B':
                print u"Problemas, ya debería venir el status a 'B'"
                pdb.set_trace()

            movement_resources.resolve_back()

            # Actualización de bases propias de un jugador (recursos, ejército)
            player = Player.objects.get(id=self.socket.session['player'])
            delay = player.game.delay - player.delay
            Greenlet(self.emit_bases_to_player,
                    movement_resources.base_start.player).start_later(delay)


    # Regreso de las hormigas tras un ataque sin hormiga princesa.
    # private
    def back_attack(self, attack, ants):
        if not attack.status == 'N': # Si no han conquistado la base inicial del ataque

            if not attack.status == 'B':
                print u"Problemas, ya debería venir el status a 'B'"
                pdb.set_trace()

            attack.resolve_back(ants=ants)

            # Actualización de bases propias de un jugador (recursos, ejército)
            player = Player.objects.get(id=self.socket.session['player'])
            delay = player.game.delay - player.delay
            Greenlet(self.emit_bases_to_player,attack.base_start.player
                        ).start_later(delay)


    # Ejecución de un ataque
    # private
    def finish_attack(self, attack):

        # Si no han conquistado la base inicial del ataque
        if not attack.status == 'N':

            player_start = attack.base_start.player
            player_finish = attack.base_finish.player

            delay_game = player_start.game.delay
            delay_start = delay_game - player_start.delay
            if player_finish:
                delay_finish = delay_game - player_finish.delay

            #try-except, con devolución íntegra de tropas (rool-back) si falla
            attack.resolve_attack()

            # No hay reina para conquistar, por lo que las tropas vuelven
            if attack.status == 'B':
                ants_back = Ant.objects.filter(in_attack = attack)
                Greenlet(self.back_attack, attack, ants_back
                            ).start_later(attack.seconds)

                # Actualiza eventos para el jugador atacante, tropas de vuelta.
                list_ants = []
                list_ants.append(ants_back.filter(type_ant = 'W').count())
                list_ants.append(ants_back.filter(type_ant = 'OO').count())
                list_ants.append(ants_back.filter(type_ant = 'OD').count())
                list_ants.append(ants_back.filter(type_ant = 'FO').count())
                list_ants.append(ants_back.filter(type_ant = 'FD').count())
                list_ants.append(ants_back.filter(type_ant = 'Q').count())
                Greenlet(self.emit_event_attack_to_player,attack,'attack_back'
                            , list_ants, attack.stolen_resources).start_later(
                            delay_start)


            # Hay conquista del hormiguero por parte del atacante
            elif attack.status == 'C':

                # Si al conquistar un hormiguero, ya existen ataques en contra
                attacks = Attack.objects.filter(base_finish=attack.base_finish,
                                status = 'P').exclude(id=attack.id)
                for a in attacks:
                    date_created = datetime(a.date_created.year,
                        a.date_created.month, a.date_created.day,
                        a.date_created.hour, a.date_created.minute,
                        a.date_created.second)
                    diff = (datetime.utcnow() - date_created).total_seconds()
                    try:
                        seconds = a.seconds - round(diff)
                    except:
                        import pdb
                        pdb.set_trace()
                        seconds = a.seconds - round(diff)

                    Greenlet(self.emit_event_attack_to_player, attack,
                            'attack_receive', seconds=seconds)




                # Actualización del mapa de juego (coordenadas, nombre de dueños de cada hormiguero..)
                # Greenlet game...
                self.emit_map_to_game(attack.base_start.game)

                # Actualización de bases propias de un jugador (recursos, ejército)
                #self.emit_bases_to_player(player_start)
                Greenlet(self.emit_bases_to_player, player_start).start_later(
                            delay_start)

                # Actualización de bases propias de un jugador (recursos, ejército)
                if player_finish:
                    #self.emit_bases_to_other_player(player_finish)
                    Greenlet(self.emit_bases_to_other_player, player_finish).start_later(delay_finish)


            # Reportes
            # Greenlet emit
            # Refactor emit report
            invisible = attack.battle_report.invisible
            Greenlet(self.emit_battle_report_to_player, attack, invisible
                        ).start_later(delay_start)

            if player_finish:
                # Greenlet emit
                # Refactor emit report
                Greenlet(self.emit_battle_report_to_other_player, player_finish
                            ,attack, invisible=False).start_later(delay_finish)

            # Actualiza la información de estadísticas de la partida
            # Greenlet game
            self.emit_players_statistics_to_game(attack.base_start.game)

            if player_finish:
                game = player_finish.game
                if player_finish.is_player_over():
                    # Greenlet game
                    self.emit_to_game(game.id, 'player_over',
                                        player_finish.id, player_finish.name)
                if game.is_game_over():
                    # Greenlet game
                    self.emit_to_game(game.id, 'game_over',
                                        attack.base_start.player.id,
                                        attack.base_start.player.name)



    # Impacto de un 'movimiento de recursos'.
    def finish_movement(self, movement_resources, seconds):
        # Si no han conquistado la base inicial del movimiento
        if not movement_resources.status == 'N':

            movement_resources.resolve()

            Greenlet(self.back_movement,movement_resources).start_later(seconds)

            # Actualización de bases propias de un jugador (recursos, ejército)
            player_finish = movement_resources.base_finish.player
            if player_finish:
                delay_finish = player_finish.game.delay - player_finish.delay
                Greenlet(self.emit_bases_to_other_player, player_finish
                            ).start_later(delay_finish)

            # Actualiza evento (vuelta del envío de recursos) para emisor
            num_workers = Ant.objects.filter(type_ant = 'W',
                            in_movement_resources = movement_resources).count()
            if movement_resources.success:
                resources = 0
            else:
                resources = movement_resources.resources

            player = movement_resources.base_start.player
            delay_start = player.game.delay - player.delay
            Greenlet(self.emit_event_movement_to_player, movement_resources,
                        'resource_back', num_workers,resources).start_later(
                        delay_start)

            # Envía Reporte
            Greenlet(self.emit_movement_report_to_player, movement_resources
                        ).start_later(delay_start)



    # Regreso de las hormigas tras un apoyo a un hormiguero vacío.
    # private
    def back_uphold(self, uphold, ants):
        # Si no han conquistado la base inicial del ataque
        if not uphold.status == 'N':

            if not uphold.status == 'B':
                import pdb
                print u"Problemas, ya debería venir el status a 'B'"
                pdb.set_trace()
                uphold.status = 'B' # ya debería de venir con éste status
            uphold.resolve_uphold(ants=ants)

        # Actualización de bases propias de un jugador (recursos, ejército)
        player = Player.objects.get(id=self.socket.session['player'])
        delay = player.game.delay - player.delay
        Greenlet(self.emit_bases_to_player, uphold.base_start.player
                    ).start_later(delay)

    # Impacto de un apoyo
    # private
    def finish_uphold(self, uphold):

        # Si no han conquistado la base inicial del ataque
        if not uphold.status == 'N':

            uphold.resolve_uphold()
            delay = uphold.base_start.game.delay-uphold.base_start.player.delay

            # Si el hormiguero está conquistado o destruido al llegar.
            if uphold.status == 'B':
                ants_back = Ant.objects.filter(in_uphold = uphold)
                Greenlet(self.back_uphold, uphold, ants_back).start_later(uphold.seconds)

                list_ants = []
                list_ants.append(ants_back.filter(type_ant = 'W').count())
                list_ants.append(ants_back.filter(type_ant = 'OO').count())
                list_ants.append(ants_back.filter(type_ant = 'OD').count())
                list_ants.append(ants_back.filter(type_ant = 'FO').count())
                list_ants.append(ants_back.filter(type_ant = 'FD').count())
                list_ants.append(ants_back.filter(type_ant = 'Q').count())

                Greenlet(self.emit_event_to_player, uphold, 'uphold_back',
                            list_ants).start_later(delay)

            else:
                # Actualización de bases (recursos, ejército)
                Greenlet(self.emit_bases_to_player, uphold.base_start.player).start_later(delay)
                
            #reporte
            Greenlet(self.emit_uphold_report_to_player, uphold
                        ).start_later(delay)



    def on_uphold(self, base_start_id, base_finish_id, ants):

        player = Player.objects.get(id=self.socket.session['player'])
        delay = player.game.delay - player.delay
        base_start = get_object_or_404(Base, id=base_start_id)
        base_finish = get_object_or_404(Base, id=base_finish_id)
        
        # Comprobaciones de seguridad
        if base_start not in player.bases.all():
            self.emit_to_player('exception', u'El hormiguero de origen no '
                                + 'te pertenece.', 'error')
            return False
        elif base_finish not in player.bases.all():
            self.emit_to_player('exception', u'El hormiguero de destino no '
                                + 'te pertenece.', 'error')
            return False

        # Creación del apoyo
        try:
            uphold = Uphold(status='P', base_start=base_start,
                            base_finish=base_finish)
            uphold.save(ants = ants)
        except Exception as e:
            self.emit_to_player('exception', e[0], 'error')
            return False

        # El evento de apoyo se produce en el futuro.
        Greenlet(self.finish_uphold, uphold).start_later(uphold.seconds)

        # Actualización de bases propias de un jugador (recursos, ejército)
        Greenlet(self.emit_bases_to_player, player).start_later(delay)

        # Actualiza eventos para el jugador atacante.
        list_ants = [ants['ant_0'], ants['ant_1'], ants['ant_2'], ants['ant_3'],
                        ants['ant_4'], ants['ant_5']]
                        
                        
        Greenlet(self.emit_event_uphold_to_player, uphold, 'uphold_start',
                    list_ants).start_later(delay)

        return True



    """
    def on_atttack(self, base_start_id, base_finish_id, ants):

        player = Player.objects.get(id=self.socket.session['player'])
        delay = player.game.delay - player.delay
        #return False

        Greenlet(self.attack, base_start_id, base_finish_id, ants,
                    player, delay).start_later(delay)
    """



    #def attack(self, base_start_id, base_finish_id, ants, player, delay_start):
    def on_attack(self, base_start_id, base_finish_id, ants):

        player = Player.objects.get(id=self.socket.session['player'])
        delay_start = player.game.delay - player.delay

        # Se obtienen instancias necesarias
        base_start = get_object_or_404(Base, id=base_start_id)
        base_finish = get_object_or_404(Base, id=base_finish_id)

        # Comprobaciones de seguridad
        if base_start not in player.bases.all():
            self.emit_to_player('exception', u'El hormiguero de origen no '
                                + 'te pertenece!', 'error')
            return False
        elif base_finish in player.bases.all():
            self.emit_to_player('exception', u'El hormiguero destino es '
                                + 'propio. No puedes atacar.', 'error')
            return False

        # Creación del ataque
        try:
            attack = Attack(status='P', base_start=base_start,
                            base_finish=base_finish)
            attack.save(ants = ants)
        except Exception as e:
            self.emit_to_player('exception', e[0], 'error')
            return False
            # return False, error

        # El evento no se ejecuta inmediatamente.
        Greenlet(self.finish_attack, attack).start_later(attack.seconds)

        # Actualización de bases propias de un jugador (recursos, ejército)
        Greenlet(self.emit_bases_to_player, player).start_later(delay_start)

        # Envío de evento para el jugador atacante
        list_ants = [ants['ant_0'], ants['ant_1'], ants['ant_2'], ants['ant_3']
                        , ants['ant_4'], ants['ant_5']]
        Greenlet(self.emit_event_attack_to_player, attack, 'attack_start',
                    list_ants).start_later(delay_start)

        # Envío de evento para el jugador que recibe el ataque
        if base_finish.player:
            delay_finish = base_finish.game.delay - base_finish.player.delay
            Greenlet(self.emit_event_attack_to_other_player, base_finish.player
                        ,attack, 'attack_receive').start_later(delay_finish)

        return True



    # Inicio de un movimiento de recursos
    def on_movement_resources(self, base_start_id, base_finish_id, resources):

        try:
            player = Player.objects.get(id=self.socket.session['player'])
            base_start = Base.objects.get(id=base_start_id)
            base_finish = Base.objects.get(id=base_finish_id)
            delay = player.game.delay - player.delay
        except Exception as e:
            self.emit_to_player('exception', e[0], 'error')
            return False
            # error = e[0]
            #return False, error

        if base_start not in player.bases.all():
            self.emit_to_player('exception', u'Situación ilegal, el hormiguero de origen no te pertenece!', 'error')
            return False
            #return False, error

        elif not base_finish.player:
            self.emit_to_player('exception', u'Situación ilegal, el hormiguero de destino no pertenece a nadie.', 'error')
            return False
            #return False, error

        try:
            movement_resources = MovementResources(status='P',
                        base_start=base_start, base_finish=base_finish,
                        resources=resources)
            movement_resources.save()
        except Exception as e:
            self.emit_to_player('exception', e[0], 'error')
            return False
            #return False, error

        Greenlet(self.finish_movement, movement_resources, movement_resources.seconds).start_later(movement_resources.seconds)

        # Actualización de bases propias de un jugador (recursos, ejército)
        # self.emit_bases_to_player(player)
        Greenlet(self.emit_bases_to_player, player).start_later(delay)

        num_workers = Ant.objects.filter(in_movement_resources = movement_resources).count()

        delay_finish = delay # siempre que sean el mismo
        Greenlet(self.emit_event_movement_to_player, movement_resources,
                'resource_receive', num_workers,
                movement_resources.resources).start_later(delay_finish)

        return True


    # Construcción de una nueva hormiga.
    # Comprobar que type_ant es W, OO o lo que sea....
    def on_new_ant(self, type_ant, num_ants, id_base):
        """
        """
        self.log('#{1} for player {0}'.format(self.socket.session['nickname'],
                                                type_ant))

        try: # check num_ants is a int
            player = Player.objects.get(id=self.socket.session['player'])
            game = Game.objects.get(id=self.game)
            delay = game.delay - player.delay
            base = Base.objects.get(id=id_base)
            num_ants = int(num_ants)
        except:
            error = 'Envía un número válido, por favor.'
            return False, error

        # comprobar que esta base es del player
        if base not in player.bases.all():
            error = u'Situación ilegal, éste hormiguero no te pertece'
            #self.emit_to_player('exception', error, 'error')
            return False, error
        try:
            # CHECK THE COST OF NEW ANTS.
            check_new_ants(type_ant, num_ants, base)
            new_ants = []
            for i in range(num_ants):
                new_ants.append(Ant(type_ant=type_ant, base=base,
                        player=player, position='C'))
            Ant.objects.bulk_create(new_ants)
        except Exception as e:
            error = e[0]
            #self.emit_to_player('exception', error, 'error')
            return False, error

        # Actualiza la información de estadísticas de la partida
        # Greenlet game
        self.emit_players_statistics_to_game(game)

        # Actualización de bases propias de un jugador (recursos, ejército)
        Greenlet(self.emit_bases_to_player, player).start_later(delay)

        return True

    def initialize(self):
        self.logger = logging.getLogger("socketio.ant")
        self.log("Socketio session GAME started")

    def log(self, message):
        self.logger.info("[{0}] {1}".format(self.socket.sessid, message))

    def on_join(self, game):
        self.game = game
        self.join(game)
        return True

    def on_nickname(self, nickname, role):

        """
            New player on game.
            Number of players limited: 2. In (0, 0), otro en (3, 3)

            Map Positions:
            (0, 1, 2, 3, 4, 5, 6, 7)
            (0, 1, 2, 3, 4, 5, 6, 7)
            (0, 1, 2, 3, 4, 5, 6, 7)
            (0, 1, 2, 3, 4, 5, 6, 7)
            (0, 1, 2, 3, 4, 5, 6, 7)
            (0, 1, 2, 3, 4, 5, 6, 7)
            (0, 1, 2, 3, 4, 5, 6, 7)
            (0, 1, 2, 3, 4, 5, 6, 7)

            Map:
            (X, c, c, c, c, c, c, X)
            (c, c, O, c, c, O, c, c)
            (c, O, c, c, c, c, O, c)
            (c, c, c, S, S, c, c, c)
            (c, c, c, S, S, c, c, c)
            (c, O, c, c, c, c, O, c)
            (c, c, O, c, c, O, c, c)
            (X, c, c, c, c, c, c, X)

            X = 4 Players
            O = 8 Free Bases
            S = 4 Special Bases
        """


        try:
            game = Game.objects.get(id=self.game)
            player = Player(name=nickname, session_id=self.socket.sessid, game=game, bonus=role)
            player.save()
            delay = game.delay - player.delay
            if game.players.count() == 1:
                x_coordinate, y_coordinate = 0, 0
            elif game.players.count() == 2:
                x_coordinate, y_coordinate = 7, 7
        except Exception as e:
            self.emit_to_player('exception', e[0], 'error')
            return False

        base = Base.objects.get(game=game, x_coordinate=x_coordinate, y_coordinate=y_coordinate)
        base.player = player
        base.resources = 4000 # provisional (2000)
        base.save()

        new_workers_ants = []
        for i in range(10): # + 10 Workers
            new_workers_ants.append(Ant(type_ant='W', base=base,
                    player=player, position='C'))
        Ant.objects.bulk_create(new_workers_ants)

        self.log('New player connected, Nickname: {0}'.format(nickname))
        self.log('New player connected, Game: {0}'.format(game.id))

        self.socket.session['player'] = object()
        self.socket.session['player'] = player.id

        # Actualiza la información de estadísticas de la partida
        self.emit_players_statistics_to_game(game)

        # Actualización de bases propias de un jugador (recursos, ejército)
        self.emit_bases_to_player(player)

        # Actualización del mapa de juego (coordenadas, nombre de dueños de cada hormiguero..)
        if role == 'S': # Speed Bonus
            self.emit_map_to_game(game, player.id, True)
        else:
            self.emit_map_to_game(game, player.id)
        Greenlet(self.refresh_map, game).start_later(30)


        self.emit_update_delay(player)

        # Iniciar la actualización automática de los datos de cada hormiguero de un jugador (recursos, ejército).
        # self.refresh_base(player, None)
        if game.players.count() == 2:
            self.emit_to_game(game.id, 'game_start')
            self.refresh_base(game.players.all()[0], None)
            self.refresh_base(game.players.all()[1], None)



        return True
        #return True, player

    """
    def recv_connect(self):

        self.log('New Player Connected')
        # asociar al game, hasta un límite
        nickname = self.socket.session['nickname']
        self.broadcast_event('announcement', '%s has connected' % nickname)
        self.broadcast_event('nicknames', self.nicknames)
        return True
    """

    def recv_disconnect(self):
        # Remove nickname from the list.
        self.log('Player Disconnected')

        if 'player' in self.session:
            player = Player.objects.get(id=self.socket.session['player'])
            player.delete()

        game = Game.objects.get(id=self.game)

        # Actualiza la información de estadísticas de la partida
        self.emit_players_statistics_to_game(game)

        self.disconnect(silent=True)
        return True


@namespace('/chat')
class ChatNamespace(BaseNamespace, RoomsMixin, BroadcastMixin):
    nicknames = []

    def initialize(self):
        self.logger = logging.getLogger("socketio.chat")
        self.log("Socketio session CHAT started")

    def log(self, message):
        self.logger.info("[{0}] {1}".format(self.socket.sessid, message))

    def on_join(self, room):
        self.room = room
        self.join(room)
        return True


    def on_nickname(self, nickname):
        self.log('Nickname: {0}'.format(nickname))
        self.nicknames.append(nickname)
        self.socket.session['nickname'] = nickname
        self.broadcast_event('announcement', '%s has connected' % nickname)
        self.broadcast_event('nicknames', self.nicknames)
        return True, nickname


    def recv_disconnect(self):
        # Remove nickname from the list.
        self.log('Chat Disconnected')
        nickname = self.socket.session['nickname']
        self.nicknames.remove(nickname)
        self.broadcast_event('announcement', '%s has disconnected' % nickname)
        self.broadcast_event('nicknames', self.nicknames)
        self.disconnect(silent=True)
        return True

    def on_user_message(self, msg):
        msg = unicode(msg)
        self.log('User message: {0}'.format(msg))
        self.emit_to_room(self.room, 'msg_to_room',
            self.socket.session['nickname'], msg)
        return True
